### Version History

### Integration v.01 changes:
### adding the player class and displaying the player object on the screen

###Restructure v.02 changes:
### Testing key events within the main game loop
### Adding transition from game loop back to shell loop

### Restructure v.01 changes
### Restructure Code as per program structure file

### v.09 changes
### separation of textobjects into a separate class
### creation of menu function to handle display of menu items

### v.08 changes
### add game over screen which displays final score, and asks the player whether to start again, or quit

### v.07 changes:
### add score and lives variables to the test function
### test the transition from the game to the game over screen

### v.06 changes:
### define a function to show the test display, and start the function when start is selected and 'enter' is pressed.
### This test function is a placeholder for the main game function running inside the main shell loop

### v.05 changes:
### create test display to test starting the game

### v.04 changes:
### Test key events to switch selection and test quitting the game

### v.03 changes:
### introduce menumode and selected variables
### define key events to switch selection if in menumode

### v.02 changes:
### changes text displayed on screen after a short pause
### creates new main class to initialise the game


###1. Import modules:

import pygame
from pygame.locals import *
import sys
import os

### 2: Declare public variables
### these variables need to be accessible to various functions in various classes


screenWidth = 800
screenHeight = 600
screenSize = screenWidth, screenHeight

"""menumode keeps track of the gamestate - allowed values are: -2 - opening screen is displayed, no menu available, 0 - control map is displayed, 1 - start menu displayed, -1 - main game loop running, no menu displayed, 2 - restart menu displayed"""
menumode = -2

"""the selected variable keeps track of which menuitem the player has selected. It can have the following values: 0 - no selectable item displayed, 1 - first/upper menuitem selected; -1 - lower/second menuitem selected"""
selected = 0

score = 0
finalScore = 0



enemyMinx = 0
enemyMaxX = screenWidth
enemyDown = 32
moveSpeed = 1


### 3. Define classes

class BugsMain:

	def __init__(self):
		
		##initialise pygame
		pygame.init()
		
		pygame.key.set_repeat(1, 1)
		
		
		## create the display
		self.screen = pygame.display.set_mode(screenSize)
		pygame.display.set_caption("Bugs!")
			
		self.background = pygame.Surface(self.screen.get_size()) #ensures background is the same size as the game window
		self.background = self.background.convert()
		self.background.fill((0, 0, 0))
		
		## make the background visible and refresh
		self.screen.blit(self.background, (0, 0))
		pygame.display.flip()

	def MenuKeyMap(self):
		"""This function displays the keymap to the player and waits for any key to be pressed before transition to the start/quit menu"""
		
		global menumode
		global selected
		
		self.screen.blit(self.background, (0,0))
		pygame.display.flip()
		
		
		##initialise text objects
		
		textControls = BugsText(None, 64)
		textControls.write("Controls", (0, 0, 255))
		textControls.adjustPos(0, -230)
		
		textKeyNav = BugsText(None, 36)
		textKeyNav.write("Up and Down arrows - navigate menu", (164, 164, 164))
		textKeyNav.adjustPos(0, -165)
		
		textEnter = BugsText(None, 36)
		textEnter.write("Enter - confirm selection", (164, 164, 164))
		textEnter.adjustPos(0, -90)
		
		textQuit = BugsText(None, 36)
		textQuit.write("Esc - quit", (164, 164, 164))
		textQuit.adjustPos(0, -15)
		
		textMove = BugsText(None, 36)
		textMove.write("Left and Right arrows - move player", (164, 164, 164))
		textMove.adjustPos(0, 60)
		
		textShoot = BugsText(None, 36)
		textShoot.write("Spacebar - fire missile", (164, 164, 164))
		textShoot.adjustPos(0, 135)
		
		textContinue = BugsText(None, 24)
		textContinue.write("Press any key to continue", (128, 128, 128))
		textContinue.adjustPos(0, 200)
	
	
		## display text objects
		
		self.screen.blit(textControls.message, textControls.pos)
		self.screen.blit(textKeyNav.message, textKeyNav.pos)
		self.screen.blit(textEnter.message, textEnter.pos)
		self.screen.blit(textQuit.message, textQuit.pos)
		self.screen.blit(textMove.message, textMove.pos)
		self.screen.blit(textShoot.message, textShoot.pos)
		self.screen.blit(textContinue.message, textContinue.pos)
		pygame.display.flip()
		
		"""This function waits for input from the player once all text objects are displayed"""
		
		
	
	
	
	
	
	
	
	def MenuStart(self):
		"""This function displays the Start/Quit menu with 'Start selected and modifies the 'selected' variable accordingly"""
		
		global selected
		
		self.screen.blit(self.background, (0,0))
		pygame.display.flip()
		
		textStartS = BugsText(None, 48)
		textStartS.write("START", (255, 64, 64))
		textStartS.adjustPos(0, -50)
		
		textQuit = BugsText(None, 48)
		textQuit.write("QUIT", (166, 166, 166))
		textQuit.adjustPos(0, 50)
		
		self.screen.blit(textStartS.message, textStartS.pos)
		self.screen.blit(textQuit.message, textQuit.pos)
		pygame.display.flip()
		
		
		selected = 1
		return
		
		"""This function waits for input from the player once text objects are displayed"""
		
	
	
	
	def MenuStartQSelect(self):
		"""This function is called when the selection in the start/quit menu is reversed. 'Quit' is now displayed as selected, and the 'selected' variable modified accordingly"""
		
		global selected
		
		self.screen.blit(self.background, (0,0))
		pygame.display.flip()
		
		textStart = BugsText(None, 48)
		textStart.write("START", (166, 166, 166))
		textStart.adjustPos(0, -50)
		
		
		textQuitS = BugsText(None, 48)
		textQuitS.write("QUIT", (255, 64, 64))
		textQuitS.adjustPos(0, 50)
		
		self.screen.blit(textStart.message, textStart.pos)
		self.screen.blit(textQuitS.message, textQuitS.pos)
		pygame.display.flip()
		
		selected = -1
		return
		
		"""This function waits for input from the player once text objects are displayed"""
		
	
	def MenuGameOver(self):
		"""This function deals with the transition from the MainGame loop back to the shell loop. It displays the players final scor and asks if they want to play again."""
		
		global menumode
		global score
		global selected
		
		finalScore = getScore(self)
		
		self.screen.fill((0, 0, 0))
		pygame.display.flip()
		
		textGameOver = BugsText(None, 128)
		textGameOver.write("GAME OVER!", (255, 0, 0))
		textGameOver.adjustPos(0, 0)
		self.screen.blit(textGameOver.message, textGameOver.pos)
		pygame.display.flip()
		
		pygame.time.delay(2500)
		
		self.screen.blit(self.background, (0, 0))
		pygame.display.flip()
		
		
		## get the last value of score and display it as final score
		textFinalScore = BugsText(None, 56)
		textFinalScore.write("Your final score is: " + finalScore, (0,255,0))
		textFinalScore.adjustPos(0, -75)
		
		textPlayAgain = BugsText(None, 48)
		textPlayAgain.write("Select 'Start' to play again or 'Quit' to exit", (164, 164, 164))
		textPlayAgain.adjustPos (0, 50)
		
		self.screen.blit(textFinalScore.message, textFinalScore.pos)
		self.screen.blit(textPlayAgain.message, textPlayAgain.pos)
		pygame.display.flip()
		
		pygame.time.delay(5000)
		menumode = 1
		selected = 1
		
		MainWindow.MenuStart()
		
		
		
	
	def MainGameLoop(self):
		"""This is where the game will run inside the shell loop"""
		
		global menumode
		global score
		
		score = 0
		lives = 3
		
		scoreDisplay = getScore(self)
		
		### initialise new background
		bg = os.path.join("image", "space_bg.png")
		self.background2 = pygame.image.load(bg).convert()
		
		
		### initialise hostiles
		
		enemyMinx = 0
		enemyMaxX = screenWidth
		enemyDown = 32
		moveSpeed = 3
		
		enemy1 = Enemy([0,0])
		
		enemy1 = pygame.sprite.RenderUpdates()
		
		eList = populateEnemies(4, 50, 64, 20, 20)
		for position in eList:
			enemy1.add(Enemy(position))
		
		
		### initialise text displayed
		textScore = BugsText(None, 24)
		textScore.write("Score: " + scoreDisplay, (0, 0, 255))
		textScore.adjustPos(350, -265)
		
		
		### initialise player avatar
		
		ship = Player([0,0])
		ship = pygame.sprite.RenderUpdates()
		playerPosition = [400,530]
		ship.add(Player(playerPosition))
		
		
		
		
		self.screen.blit(self.background2, (0,0))
		pygame.display.flip()
		
		
		
		
		
		
		##self.screen.blit(textScore.message, textScore.pos) - to be handled by update function
		##pygame.display.flip()
		
		##wrapping condition - game runs until the player runs out of lives
		
		while lives > 0:
		
		
			enemy1.update(pygame.time.get_ticks(), enemyMinx,enemyMaxX,enemyDown, moveSpeed)
			ship.update(pygame.time.get_ticks())
			rectlist = enemy1.draw(self.screen), ship.draw(self.screen)
			self.screen.blit(textScore.message, textScore.pos)
			
			pygame.display.flip()
		
		
	
	
	
	
			pygame.time.delay(40)
	
			
			enemy1.clear(self.screen, self.background2)
			ship.clear(self.screen, self.background2)
			
		
			
	
		
	
	
	
		### end of placeholder
		
		"""Once the variable 'lives' has reached 0 this function changes the value of menumode and uses the MenuGameOver function to display the gameover screen"""
		
		menumode = 2
		MainWindow.MenuGameOver()
	
	

	def ShellLoop(self):
	
	
		"""This is where all the events in the game will run"""
		
		global menumode
		global selected
		
		### initialise opening display
		textWelcome = BugsText(None, 48)
		textWelcome.write("Splat the bugs and win fabulous prizes!", (255, 0, 0))
		textWelcome.adjustPos(0, 0)
		
		self.screen.blit(textWelcome.message, textWelcome.pos)
		pygame.display.flip()
		
		pygame.time.delay(2000)
		
		menumode = 0
		MainWindow.MenuKeyMap()





		while 1:
			for event in pygame.event.get():
				if event.type == QUIT:
					return
		
				elif event.type == KEYDOWN:
		
					if menumode == 0: ## pressing any key during this stage triggers MenuStart
						menumode = 1
						MainWindow.MenuStart()
						
					elif event.key == K_ESCAPE: #player chooses to exit the game
						return
					
					elif menumode == 1: #starting menu is displayed
						if event.key == K_UP:
							if selected == -1: # 'quit' is currently selected,
								MainWindow.MenuStart()
					
					
						if event.key == K_DOWN:
							if selected == 1: # 'start' is currently selected
								MainWindow.MenuStartQSelect()
					
					
						if event.key == K_RETURN:
							if selected == -1: ## if 'quit' is selected when return key pressed, game exits
								sys.exit("bye-bye!")
					
							elif selected == 1: ## if 'start' is selected when return key pressed, main game starts
								selected = 0 #reset selected variable
								menumode = -1 #set menumode to indicate that the main game is now running
								MainWindow.MainGameLoop()
		








class BugsText(pygame.font.Font):
	"""This is the class that will handle text objects and their attributes"""
	
	def __init__(self, font_name, size):
		pygame.font.Font.__init__(self, None, size)
		
	def write(self, message, color):
		self.message = self.render(message, 1, color)
		self.pos = self.message.get_rect()
		
	
	def adjustPos(self, adjustX, adjustY):
		self.pos.centerx = screenWidth/2 + adjustX
		self.pos.centery = screenHeight/2 +adjustY





### Sprite class goes here

### Player class goes here

class Player(pygame.sprite.Sprite):
	pos=[400,530]
	missileMoving=False
	missileY=pos[1]
	image = None
	
	##______________________ ##
	
	def __init__(self, playerPos):
		pygame.sprite.Sprite.__init__(self) #initialise the parent class
		
		if Player.image is None:
			self.loadImages()
            # This is the first time this class has been instantiated.
			# So, load the image for this and all subsequence instances
			self.image = self.temp
			self.rect = self.image.get_rect()

		
			self.rect.topleft = playerPos
			self.nextUpdateTime = 0
	#______________________##
	
	def loadImages(self):
		self.ship = []
		hero = os.path.join("image", "player.gif")
		self.temp=pygame.image.load(hero).convert()
		key = self.temp.get_at((1, 1)) #find pix and use as trans colour
		self.temp.set_colorkey(key)
		
		self.ship.append(self.temp)
	
	def update(self, currentTime):
		if self.nextUpdateTime < currentTime:
	
			for event in pygame.event.get():
				if event.type == QUIT:
					sys.exit()

				elif event.type == KEYDOWN:

					if event.key == K_ESCAPE: #player chooses to exit the game
						sys.exit()

					if event.key==K_LEFT:
						self.moveLeft()
                    
					if event.key==K_RIGHT:
						self.moveRight()
                			
					if event.key==K_SPACE:
						print "pewpewpewpew"
		self.nextUpdateTime = currentTime + 10
	def moveLeft(self):
		self.pos[0]-=5        #move  5 pixels to the left
		self.rect.topleft=(self.pos[0],self.pos[1]) #use topleft as reference
       
       		#print self.pos[0]     #see down for this function
       		
		if self.rect.topleft==(0,self.pos[1]):       #if the hero reaches the border of the screen
           		self.pos[0]=self.pos[0]+5                           #the last movement is ignored because five pixels are added
      
#_______________________________#  same as above function
	def moveRight(self):                                                
   		self.pos[0]+=5
   		self.rect.topright=(self.pos[0]+64,self.pos[1]) #using the top right corner of the image which is situated 32 pixels after the
                                       
       		#print self.pos[0]
       		#print self.imagerect.topright[0]

   		if self.rect.topright[0]>=800:                       # use the corner as a stopper preventing the image from moving
			self.pos[0]=self.pos[0]-5


	




### Missile class goes here

### Enemy class goes here

class Enemy(pygame.sprite.Sprite):
	"""
	This class can be used to create an enemy object. Functions and notes are as follows:
	
	__init__
	defines initial variables used within the class
	
	loadimages
	loads each image for the enemy animation
	an error message will be brought up if correct images are not found
	
	update
	use RenderUpdates()
	updates animation frame each time update is called
	takes the following parameters
	currentTime
	obtained from pygame.time.get_ticks()
	enemyMinx
	obtained from level class
	enemyMaxX
	obtained from level class
	enemyDown
	obtained from level class
	moveSpeed
	obtained from level class
	move
	controls the enemy path
	takes the following parameters
	left
	obtained from update function via enemyMinx
	right
	obtained from update function via enemyMaxX
	down
	obtained from update function via enemyDown
	speed
	obtained from update function via moveSpeed
	"""
	image = None
	global enemyMinx
	global enemyMaxX
	global enemyDown
	global moveSpeed
	
	#########################################
	#Set inital variables
	#########################################
	
	def __init__(self, initialPosition):
		pygame.sprite.Sprite.__init__(self)
		
		
		
		if Enemy.image is None:
			self.loadImages()
			# This is the first time this class has been instantiated.
			# So, load the image for this and all subsequence instances
			self.image = self.temp
			self.rect = self.image.get_rect()
			self.rect.topleft = initialPosition
			self.frame = 0
			self.nextUpdateTime = 0
			self.goingRight = True
			self.goingLeft = False
			self.goingDownLeft = False
			self.goingDownRight = False
			self.downTempL = 0
			self.downTempR = 0
			self.tempTime = 0
	
	
	#########################################
	#loadImages Function
	#Loads images for enemy animation
	#########################################
	
	def loadImages(self):
		self.enemy1 = []
		
		try:
			#frame 1
			a1f1 = os.path.join("image","a1f1.gif")
			self.temp = pygame.image.load(a1f1).convert()
			key = self.temp.get_at((1, 1)) #find pix and use as trans colour
			self.temp.set_colorkey(key)
			
			#add frame
			self.enemy1.append(self.temp)
			
			#frame 2
			a1f2 = os.path.join("image","a1f2.gif")
			self.temp = pygame.image.load(a1f2).convert()
			key = self.temp.get_at((1, 1))
			self.temp.set_colorkey(key)
			
			#add frame
			self.enemy1.append(self.temp)
			
			#frame 3
			a1f3 = os.path.join("image","a1f3.gif")
			self.temp = pygame.image.load(a1f3).convert()
			key = self.temp.get_at((1, 1))
			self.temp.set_colorkey(key)
	
		except pygame.error:
			#elaborate error message
			self.Font = pygame.font.Font(None, 20)
			self.textOpen = self.Font.render("Images totally cant be found. Closing...", 1, (255, 255, 255))
			self.textOpenPos = self.textOpen.get_rect()
			self.textOpenPos.centerx = background.get_rect().centerx
			self.textOpenPos.centery = background.get_rect().centery
			
			screen.blit(self.textOpen, self.textOpenPos)
			pygame.display.update()
			pygame.time.delay(2000)
			sys.exit(0)
		
		#add frame
		self.enemy1.append(self.temp)
	
	#########################################
	#Update function. Used with RenderUpdates()
	#########################################
	
	def update(self, currentTime, enemyMinx, enemyMaxX, enemyDown, moveSpeed): #pygame.time.get_ticks(), move function
	
		if self.nextUpdateTime < currentTime:
			if self.tempTime == 2:
				
					#moves to the next frame, resets if there are no more
				self.frame += 1
				if self.frame >= len(self.enemy1):
					self.frame = 0
				self.image = self.enemy1[self.frame]
				
			
			
				self.tempTime = 0
		
			self.tempTime +=1
			self.move(enemyMinx, enemyMaxX, enemyDown, moveSpeed)
			self.nextUpdateTime = currentTime + 10
	
	#########################################
	#Move function
	#########################################
	
	def move(self, left, right, down, speed):
	
		for count in range(speed): #used to differ from animation speed
		
			#########################################
			#Which way to go
			#########################################
			
			if self.rect.right == right - 1:
				self.goingRight = False
				self.goingLeft = False
				self.goingDownRight = True
				self.goingDownLeft = False
			
			if self.rect.left == left + 1:
				self.goingRight = False
				self.goingLeft = False
				self.goingDownRight = False
				self.goingDownLeft = True
			
			if self.downTempL == down + 1:
				self.downTempL = 0
				self.goingRight = True
				self.goingLeft = False
				self.goingDownRight = False
				self.goingDownLeft = False
			
			if self.downTempR == down + 1:
				self.downTempR = 0
				self.goingRight = False
				self.goingLeft = True
				self.goingDownRight = False
				self.goingDownLeft = False
				
			#########################################
			#Movement
			#########################################
			
			if self.goingDownLeft:
				self.rect.bottom += 1
				self.downTempL += 1
			
			
			if self.goingDownRight:
				self.rect.bottom += 1
				self.downTempR += 1
				
			
			if self.goingRight:
				self.rect.left +=1
			
			if self.goingLeft:
				self.rect.right -=1


### Shield class goes here



### 4. Define functions

def getScore(self):
	global score
	return(str(score))

def populateEnemies(rowCount, xGap, yGap, xMargin, yMargin):
	"""
	rowCount
		number of rows of enemies to fill
	xGap
		gap from top left of enemy to top left of enemy horizontally adjacent
	yGap
		gap from top left of enemy to top left of enemy vertically adjacent
	xMargin
		distance from left border to 1st enemy, and right border to last enemy (give or take xGap)
	yMargin
		distance from top border to 1st row
	"""
	_enemyList = []
	# dentist time: filling the array
	_xCount = xMargin
	while _xCount < (screenWidth - yMargin): # loop until right margin is crossed
		# fill down the column
		for i in range(0, rowCount, 1):
			_enemyList.append([_xCount, (i * yGap) + yMargin])
		# advance to next column
		_xCount += xGap
	
	return _enemyList











### Start Main class


if __name__ == '__main__':
	MainWindow = BugsMain()
	MainWindow.ShellLoop()
